package com.cbh.amadeus.common.json;

import com.cbh.amadeus.common.protocol.AmadeusMessage;
import com.cbh.amadeus.common.utils.StringUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ResolvableType;

import java.util.Deque;
import java.util.LinkedList;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ChenBiHui
 * @date 2025/4/25
 */
@Slf4j
public class Json {

    private static final String EMPTY_MESSAGE = "{}";
    private static final Map<Class<?>, JavaType> JAVA_TYPE_MAP = new ConcurrentHashMap<>(64);
    private static final ObjectMapper JSON = new ObjectMapper()
            //指定时区
            .setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"))
            //支持Jdk8的所有新特性
            .registerModule(new Jdk8Module())
            //支持Jdk8的所有时间API
            .registerModule(new JavaTimeModule())
            //支持全局可见性
            .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
            //允许序列化空对象
            .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
            //允许反序列化忽略未知字段
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);


    public static AmadeusMessage parse(String json) {
        try {
            return JSON.readValue(json, AmadeusMessage.class);
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            return new AmadeusMessage();
        }
    }

    public static <T> T parse(String json, TypeReference<T> reference) {
        try {
            return JSON.readValue(json, reference);
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    public static <T> String payloadJson(T payload) {
        try {
            return JSON.writeValueAsString(payload);
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            return EMPTY_MESSAGE;
        }
    }

    public static String toJson(AmadeusMessage message) {
        try {
            return JSON.writeValueAsString(message);
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            return EMPTY_MESSAGE;
        }
    }

    public static <T> T read(Class<?> base, Class<?> impl, String json) {
        if (StringUtil.unHasText(json)) {
            return null;
        }
        try {
            JavaType javaType = JAVA_TYPE_MAP.get(impl);
            if (javaType == null) {
                javaType = JAVA_TYPE_MAP.computeIfAbsent(impl, key -> {
                    ResolvableType type = ResolvableType.forClass(base, impl);
                    JavaType polled = read(JSON.getTypeFactory(), type).pollFirst();
                    if (polled == null) {
                        throw new IllegalArgumentException("Can't find any [JavaType] with [Resolvable] in Class [" + impl.getName() + "]");
                    }
                    return polled;
                });
            }
            return JSON.readValue(json, javaType);
        } catch (Throwable e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

    private static Deque<JavaType> read(TypeFactory factory, ResolvableType type) {
        Deque<JavaType> javas = new LinkedList<>();
        ResolvableType[] generics = type.getGenerics();
        for (ResolvableType generic : generics) {
            if (generic.hasGenerics()) {
                Deque<JavaType> newly = read(factory, generic);
                JavaType[] array = newly.toArray(new JavaType[0]);
                JavaType javaType = factory.constructParametricType(generic.resolve(), array);
                javas.addLast(javaType);
            } else {
                Class<?> resolve = generic.resolve();
                JavaType javaType = factory.constructType(resolve);
                javas.addLast(javaType);
            }
        }
        return javas;
    }
}
